﻿using System.Collections.Generic;
using System;
using System.Collections;
namespace Primer
{
    public abstract class ForwardBlockedCollection<TBlock, TValue>
    {
        private LinkedList<Block> _blocks = new LinkedList<Block>();
        private int _blockSize = 0x10000;

        public class Block : IEnumerable<TValue>
        {
            public int Count;
            public TBlock[] Items;

            public IEnumerator<TValue> GetEnumerator()
            {
                throw new NotImplementedException();
            }

            IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
        }

        public LinkedList<Block> Blocks
        {
            get { return _blocks; }
        }

        protected int BlockSize
        {
            get { return _blockSize; }
            set
            {
                if (value <= 0)
                    throw new ArgumentOutOfRangeException("value");
                _blockSize = value;
            }
        }

        protected abstract TBlock Factory(ref TValue value);

        public TValue Add(ref TValue value)
        {
            // complete existing block
            Block b;
            var last = _blocks.Last;
            if (last != null && (b = last.Value).Count < _blockSize)
            {
                b.Items[b.Count++] = Factory(ref value);
                return value;
            }
            // add new block
            var items = new TBlock[_blockSize];
            items[0] = Factory(ref value);
            _blocks.AddLast(new Block { Count = 1, Items = items });
            return value;
        }

        public TValue Add(IEnumerable<TValue> values)
        {
            int count;
            TBlock[] items, carryItems;
            // complete existing block
            Block b;
            var last = _blocks.Last;
            if (last != null && (b = last.Value).Count < _blockSize)
            {
                count = b.Count;
                items = carryItems = b.Items;
            }
            else
            {
                b = null;
                count = 0;
                items = new TBlock[_blockSize];
                carryItems = null;
            }
            // add new blocks
            TValue value = default(TValue);
            for (var e = values.GetEnumerator(); e.MoveNext(); )
            {
                value = e.Current;
                items[count++] = Factory(ref value);
                if (count != _blockSize)
                    continue;
                // add new block
                if (items != carryItems)
                    _blocks.AddLast(new Block { Count = count, Items = items });
                else
                    b.Count = count;
                count = 0;
                items = new TBlock[_blockSize];
            }
            if (count > 0)
                if (items != carryItems)
                    _blocks.AddLast(new Block { Count = count, Items = items });
                else
                    b.Count = count;
            return value;
        }
    }
}
